
//Copyright 1997-2009 Syrinx Development, Inc.
//This file is part of the Syrinx Web Application Framework (SWAF).
// == BEGIN LICENSE ==
//
// Licensed under the terms of any of the following licenses at your
// choice:
//
//  - GNU General Public License Version 3 or later (the "GPL")
//    http://www.gnu.org/licenses/gpl.html
//
//  - GNU Lesser General Public License Version 3 or later (the "LGPL")
//    http://www.gnu.org/licenses/lgpl.html
//
//  - Mozilla Public License Version 1.1 or later (the "MPL")
//    http://www.mozilla.org/MPL/MPL-1.1.html
//
// == END LICENSE ==
using System;
using System.CodeDom;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.Text;
using System.Text.RegularExpressions;

using Swaf;

namespace Swaf.CodeGen
{
	public class CodeClass
	{
		protected List<string> m_referencedAssemblies = new List<string>();
		protected NamedCodeBlock m_rootBlock;
		protected bool m_generateDll = false, m_includeDebugInfo = false;

		public static string GenBinLocation
		{
			get
			{
				string loc = Application.globalApp.resMgr.resolve("$ConfigParam1$bin\\");
				if (loc.StartsWith("file://"))
					loc = loc.Substring(7);
				string baseDir = Path.Combine(loc, @"..\GenBin\");
				DirectoryInfo di = new DirectoryInfo(baseDir);
				return di.FullName;
			}
		}

		public CodeClass(string classTemplateName, bool generateDll, bool includeDebugInfo)
			:this(classTemplateName)
		{
			m_generateDll = generateDll;
			m_includeDebugInfo = includeDebugInfo;
		}

		public CodeClass(string classTemplateName)
		{
			Assembly ass = null;
			int index = classTemplateName.LastIndexOf(".");
			if (index != -1)
				ass = Assembly.LoadWithPartialName(classTemplateName.Substring(0, index));
			if (ass == null)
			{
				index = classTemplateName.IndexOf(".");
				if(index != -1)
					ass = Assembly.LoadWithPartialName(classTemplateName.Substring(0, index));
			}
			TextReader r = new StreamReader(ass.GetManifestResourceStream(classTemplateName + ".cs"));
			string rawClassTemplate = r.ReadToEnd();
			r.Close();

			m_rootBlock = new NamedCodeBlock("root",rawClassTemplate, null);
		}

		public void addReference(string assemblyName)
		{
			m_referencedAssemblies.Add(assemblyName);
		}

		public CodeBlockDictionary CodeBlocks { get { return m_rootBlock.CodeBlocks; } }

		public string generateClassText()
		{
			return m_rootBlock.GeneratedContents;
		}

		public object createInstance(string generatedTypeName)
		{
			Microsoft.CSharp.CSharpCodeProvider provider = new Microsoft.CSharp.CSharpCodeProvider();
			ICodeCompiler compiler = provider.CreateCompiler();
			CompilerParameters compileParams = new CompilerParameters();
			foreach (string assemblyName in m_referencedAssemblies)
			{
				string name = Application.globalApp.resMgr.resolve(assemblyName);
				if (name.StartsWith("file://"))
					name = name.Substring(7);
				compileParams.ReferencedAssemblies.Add(name);
			}
			compileParams.IncludeDebugInformation = m_includeDebugInfo;

			string code = generateClassText();
			CompilerResults rc = null;
			string baseDir = GenBinLocation;
			if (!m_generateDll)
			{
				compileParams.GenerateInMemory = true;
				rc = compiler.CompileAssemblyFromSource(compileParams, code);
			}
			else
			{
				System.IO.Directory.CreateDirectory(baseDir);
				compileParams.OutputAssembly = Cmn.pathCombine(baseDir, generatedTypeName + ".dll");
				if (m_includeDebugInfo)
				{
					File.WriteAllText(Path.Combine(baseDir, generatedTypeName + ".cs"), code);
					compileParams.TempFiles = new TempFileCollection(baseDir, false);
					rc = compiler.CompileAssemblyFromFile(compileParams, Path.Combine(baseDir, generatedTypeName + ".cs"));
				}
				else
					rc = compiler.CompileAssemblyFromSource(compileParams, code);
			}

			if (rc.Errors.HasErrors)
			{
				StringBuilder buff = new StringBuilder();
				for(int p = 0; p < rc.Errors.Count; p++)
				{
					CompilerError ce = rc.Errors[p];
					buff.AppendFormat("Compile Error {0} of {1}: {2}\n", p+1, rc.Errors.Count, rc.Errors[p]);
				}
				throw new CodeGenException(buff.ToString());
			}
			object obj = rc.CompiledAssembly.CreateInstance(generatedTypeName);
			return obj;
		}

		protected IApplication App { get { return Application.currentApp; } }
		private static int s_uniqueCount = 1;
	}

	public interface ICodeBlock
	{
		ICodeBlock getGeneratedContents(StringBuilder buff);
		string GeneratedContents { get;set;}

		ICodeBlock getTemplateContents(StringBuilder buff);
		string TemplateContents { get;}
		ICodeBlock Clone();
	}

	public class TextCodeBlock : ICodeBlock
	{
		protected string m_templateContents, m_generatedContents;

		public TextCodeBlock(string templateContents)
		{
			m_templateContents = templateContents;
		}
		public TextCodeBlock(TextCodeBlock b)
		{
			m_templateContents = b.TemplateContents;
		}
		public ICodeBlock Clone() { return new TextCodeBlock(this); }

		public ICodeBlock getTemplateContents(StringBuilder buff)
		{
			buff.Append(m_templateContents);
			return this;
		}

		public string TemplateContents
		{
			get
			{
				return m_templateContents;
			}
		}

		public ICodeBlock getGeneratedContents(StringBuilder buff)
		{
			buff.Append(m_generatedContents != null ? m_generatedContents : m_templateContents);
			return this;
		}

		public string GeneratedContents
		{
			get
			{
				return m_generatedContents != null ? m_generatedContents : m_templateContents;
			}
			set { m_generatedContents = value; }
		}
	}

	public class CodeBlockDictionary : Dictionary<string, NamedCodeBlock> {}

	public class NamedCodeBlock : ICodeBlock	{
		protected NamedCodeBlock m_referencedBlock = null;
		protected CodeBlockDictionary m_globalNamedCodeBlocks = null;
		protected CodeBlockDictionary m_namedChildCodeBlocks = new CodeBlockDictionary();
		protected List<ICodeBlock> m_orderedCodeBlocks = new List<ICodeBlock>();
		protected List<NamedCodeBlock> m_generatedInstances = new List<NamedCodeBlock>();
		protected string m_generatedContents = null;
		protected string m_name = "";

		protected NamedCodeBlock(NamedCodeBlock clone)
		{
			m_referencedBlock = clone.m_referencedBlock;
			m_globalNamedCodeBlocks = clone.m_globalNamedCodeBlocks;
			m_name = clone.Name;
			foreach (ICodeBlock b in clone.m_orderedCodeBlocks)
			{
				ICodeBlock bClone = b.Clone();
				m_orderedCodeBlocks.Add(bClone);
				if (bClone is NamedCodeBlock)
					m_namedChildCodeBlocks[((NamedCodeBlock)bClone).Name] = (NamedCodeBlock)bClone;
			}
		}
		public ICodeBlock Clone() { return new NamedCodeBlock(this); }

		public NamedCodeBlock(string name, string rawContents, CodeBlockDictionary globals)
		{
			if (globals == null)
				globals = new CodeBlockDictionary();
			m_globalNamedCodeBlocks = globals;
			if (name[0] == '$')
				if (m_globalNamedCodeBlocks.TryGetValue(name, out m_referencedBlock))
					return;
				else
					m_globalNamedCodeBlocks[name] = this;

			m_name = name;
			Regex regex = new Regex(@"(.*?)/\*\*([\w\$]+)\*/(.*?)/\*\*(\2)\*/", RegexOptions.Singleline);
			MatchCollection mInfo = regex.Matches(rawContents);
			foreach (Match m in mInfo)
			{
				NamedCodeBlock block = new NamedCodeBlock(m.Groups[2].Value, m.Groups[3].Value, m_globalNamedCodeBlocks);
				m_orderedCodeBlocks.Add(new TextCodeBlock(m.Groups[1].Value));
				m_orderedCodeBlocks.Add(block);
				if(block.m_referencedBlock == null)
					m_namedChildCodeBlocks.Add(block.Name, block);
			}
			if(mInfo.Count != 0)
				m_orderedCodeBlocks.Add(new TextCodeBlock(rawContents.Substring(mInfo[mInfo.Count - 1].Index + mInfo[mInfo.Count - 1].Length)));
			else
				m_orderedCodeBlocks.Add(new TextCodeBlock(rawContents));
		}
		public string Name { get { return m_referencedBlock == null?m_name:m_referencedBlock.Name; } }
		public string TemplateContents
		{
			get
			{
				if (m_referencedBlock != null)
					return m_referencedBlock.TemplateContents;

				StringBuilder buff = new StringBuilder(2048);
				getTemplateContents(buff);
				return buff.ToString();
			}
		}
		public ICodeBlock getTemplateContents(StringBuilder buff)
		{
			if (m_referencedBlock != null)
				return m_referencedBlock.getTemplateContents(buff);

			foreach (ICodeBlock block in m_orderedCodeBlocks)
				buff.Append(block.TemplateContents);
			return this;
		}

		public string GeneratedContents
		{
			get 
			{
				if (m_referencedBlock != null)
					return m_referencedBlock.GeneratedContents;

				StringBuilder buff = new StringBuilder(2048);
				getGeneratedContents(buff);
				return buff.ToString();
			}
			set { m_generatedContents = value; }
		}
		public ICodeBlock getGeneratedContents(StringBuilder buff)
		{
			if (m_referencedBlock != null)
				return m_referencedBlock.getGeneratedContents(buff);

			if (m_generatedContents != null)
				buff.Append(m_generatedContents);
			else
			{
				foreach (ICodeBlock block in m_orderedCodeBlocks)
					block.getGeneratedContents(buff);
				foreach (ICodeBlock block in m_generatedInstances)
					block.getGeneratedContents(buff);
			}
			return this;
		}

		public NamedCodeBlock setFormatedContents(params object[] p)
		{
			if (m_referencedBlock != null)
				return m_referencedBlock.setFormatedContents(p);

			foreach(ICodeBlock block in m_orderedCodeBlocks)
				if(block is TextCodeBlock)
					block.GeneratedContents = string.Format(block.TemplateContents, p);
			return this;
		}

		public NamedCodeBlock appendFormatedContents(params object[] p)
		{
			if (m_referencedBlock != null)
				return m_referencedBlock.appendFormatedContents(p);

			NamedCodeBlock clone = new NamedCodeBlock(this);
			m_generatedInstances.Add(clone);
			foreach (ICodeBlock block in clone.m_orderedCodeBlocks)
				if (block is TextCodeBlock)
					block.GeneratedContents = string.Format(block.TemplateContents, p);
			return clone;
		}
		public CodeBlockDictionary CodeBlocks { get { return m_referencedBlock==null?m_namedChildCodeBlocks:m_referencedBlock.CodeBlocks; } }

		//protected internal Match Info { get { return m_match; } }

	}
}
